package com.xxxtea.redis;

import cn.hutool.core.convert.Convert;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;

import java.util.HashSet;
import java.util.Set;

/**
 * Redis操作助手类
 * 
 * 继承RedisTemplate，提供便捷的Redis操作方法，包括：
 * - 简单的键值设置和获取
 * - 类型转换的获取方法
 * - 模式匹配的键扫描和删除
 * - 批量操作支持
 * 
 * @author badman
 * @since 1.0.0
 */
@Slf4j
public class RedisHelper extends RedisTemplate<String, Object> {

	/**
	 * 设置键值对
	 * 
	 * 将对象值存储到Redis中，使用JSON序列化
	 * 
	 * @param key 键
	 * @param value 值对象
	 */
	public void set(String key, Object value) {
		opsForValue().set(key, value);
	}

	/**
	 * 获取指定类型的值
	 * 
	 * 从Redis中获取值并转换为指定类型
	 * 
	 * @param key 键
	 * @param clazz 目标类型
	 * @param <T> 泛型类型
	 * @return 转换后的对象，如果键不存在则返回null
	 */
	public <T> T get(String key, Class<T> clazz) {
		return Convert.convert(clazz, opsForValue().get(key));
	}

	/**
	 * 扫描并删除匹配模式的键
	 * 
	 * 使用SCAN命令查找匹配模式的键，然后批量删除
	 * 适用于大量键的删除操作，避免阻塞Redis
	 * 
	 * @param pattern 键模式，支持通配符（如：user:*）
	 */
	public void scanDelKeys(String pattern) {
		// 扫描获取匹配的键集合
		Set<String> keys = this.scanKeys(pattern);
		// 批量删除键
		this.delete(keys);
	}

	/**
	 * 扫描匹配模式的键
	 * 
	 * 使用Redis的SCAN命令安全地遍历键空间，查找匹配指定模式的键
	 * 相比KEYS命令，SCAN不会阻塞Redis服务器
	 * 
	 * @param pattern 键模式，支持通配符（如：user:*）
	 * @return 匹配的键集合，如果扫描失败则返回空集合
	 */
	public Set<String> scanKeys(String pattern) {
		// 构建扫描选项：匹配模式，每次扫描100000个键
		ScanOptions scanOptions = ScanOptions.scanOptions().match(pattern).count(100000).build();
		
		try (RedisConnection connection = getRequiredConnectionFactory().getConnection();
		     Cursor<byte[]> cursor = connection.scan(scanOptions)) {
			Set<String> keys = new HashSet<>();
			// 遍历扫描结果
			while (cursor.hasNext()) {
				keys.add(new String(cursor.next()));
			}
			return keys;
		} catch (Exception e) {
			// 记录扫描异常日志
			log.error("redis scan操作出现异常", e);
			return new HashSet<>();
		}
	}
}
